﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;

namespace ProjectEuler.Linq
{
    public static class Numeric
    {
        public static UInt64 Sum(this IEnumerable<UInt64> source)
        {
            return source.Aggregate(0ul, (sum, n) => (sum + n));
        }

        public static Int32 Product(this IEnumerable<Int32> source)
        {
            return source.Aggregate(1, (product, n) => (product * n));
        }

        public static Int32 Product(this Tuple<Int32, Int32, Int32> triple)
        {
            return triple.A * triple.B * triple.C;
        }

        public static IEnumerable<Int64> ProperDivisors(this Int64 n)
        {
            yield return 1;

            var div = 2;
            var sqrt = n.SquareRoot();

            while (div <= sqrt)
            {
                if (n.IsMultipleOf(div))
                {
                    if (div != n)
                    {
                        yield return div;
                    }

                    if (div != (n / div))
                    {
                        yield return n / div;
                    }
                }

                div++;
            }
        }

        public static Double Factorial(this Int32 n)
        {
            var result = 1.0;

            while (n > 1)
            {
                result *= n--;
            }

            return result;
        }
        
        #region Squares

        public static Boolean IsSquare(this Int32 n)
        {
            return n.SquareRoot().IsNatural();
        }

        public static Boolean IsSquare(this Int64 n)
        {
            return n.SquareRoot().IsNatural();
        }

        public static Int32 Square(this Int32 n)
        {
            return n * n;
        }

        public static Int64 Square(this Int64 n)
        {
            return n * n;
        }

        public static Double SquareRoot(this Int32 n)
        {
            return Math.Sqrt(n);
        }

        public static Double SquareRoot(this Int64 n)
        {
            return Math.Sqrt(n);
        }

        #endregion

        #region Triangular numbers

        public static Int64 Triangular(this Int32 n)
        {
            return (n * (n + 1)) / 2;
        }

        public static Boolean IsTriangular(this Int32 n)
        {
            return (8 * n + 1).IsSquare();
        }

        public static Boolean IsTriangular(this Int64 n)
        {
            return (8 * n + 1).IsSquare();
        }

        public static Int32? TriangularRoot(this Int64 n)
        {
            var term = (8 * n + 1).SquareRoot();

            var x1 = ((term - 1) / 2);
            var x2 = ((term + 1) / 2);

            if (x1.IsNatural())
            {
                return (Int32)(x1);
            }

            if (x2.IsNatural())
            {
                return (Int32)(x2);
            }

            return null;
        }

        #endregion

        #region Pentagonal numbers

        public static Boolean IsPentagonal(this Int64 n)
        {
            return (((24 * n + 1).SquareRoot() + 1) / 6).IsNatural();
        }

        #endregion

        #region Hexagonal numbers

        public static Boolean IsHexagonal(this Int64 n)
        {
            return (((8 * n + 1).SquareRoot() + 1) / 4).IsNatural();
        }

        #endregion

        public static Boolean IsAbundant(this Int32 n)
        {
            return ProperDivisors(n).Sum() > n;
        }

        public static Boolean IsEven(this Int32 n)
        {
            return (n % 2) == 0;
        }

        public static Boolean IsEven(this Int64 n)
        {
            return (n % 2) == 0;
        }

        public static Boolean IsOdd(this Int32 n)
        {
            return (n % 2) != 0;
        }

        public static Boolean IsNatural(this Double d)
        {
            return (d >= 0) && ((d % 1.0) == 0.0);
        }

        public static Func<Int32, Boolean> IsMultipleOfAny(params Int32[] divisors)
        {
            return n => divisors.Any(d => IsMultipleOf(n, d));
        }

        public static Func<Int32, Boolean> IsMultipleOfAll(params Int32[] divisors)
        {
            return n => divisors.All(d => IsMultipleOf(n, d));
        }

        public static Func<Int32, Boolean> IsMultipleOf(Int32 divisor)
        {
            return n => IsMultipleOf(n, divisor);
        }

        public static Boolean IsMultipleOf(this Int64 n, Int64 divisor)
        {
            return (n % divisor) == 0;
        }

        public static Boolean IsMultipleOf(this UInt64 n, UInt64 divisor)
        {
            return (n % divisor) == 0;
        }

        public static Boolean IsPalindrome(this Int64 n)
        {
            return IsPalindrome(n, 10);
        }

        public static Boolean IsPalindrome(this Int64 n, Int32 toBase)
        {
            var str = Convert.ToString(n, toBase);
            var digits = str.Length;

            for (int i = 0; i < digits / 2; i++)
            {
                if (str[i] != str[digits - i - 1])
                {
                    return false;
                }
            }

            return true;
        }

        public static Boolean IsPandigital(this Int64 n)
        {
            var digits = n.DecimalDigits().ToArray();

            return (1).To(digits.Length).All(i => digits.Count(d => i == d) == 1);
        }

        public static Boolean IsPrime(this Int32 n)
        {
            if (n < 2)
            {
                return false;
            }

            return IsPrime((Int64)(n));
        }

        public static Boolean IsPrime(this Int64 n)
        {
            if (n < 2)
            {
                return false;
            }

            if (n < 4)
            {
                return true;
            }

            var sqrt = Math.Sqrt(n);
            
            foreach (var prime in new SieveOfEratosthenes((Int32)(sqrt)))
            {
                if (n.IsMultipleOf(prime))
                {
                    return false;
                }
            }

            return true;
        }
    }
}
